package owl2vowl.parser.vowl.classes;

import java.util.Iterator;
import java.util.Set;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataAllValuesFrom;
import org.semanticweb.owlapi.model.OWLDataExactCardinality;
import org.semanticweb.owlapi.model.OWLDataHasValue;
import org.semanticweb.owlapi.model.OWLDataMaxCardinality;
import org.semanticweb.owlapi.model.OWLDataMinCardinality;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLDataPropertyExpression;
import org.semanticweb.owlapi.model.OWLDataRange;
import org.semanticweb.owlapi.model.OWLDataSomeValuesFrom;
import org.semanticweb.owlapi.model.OWLDatatype;
import org.semanticweb.owlapi.model.OWLDisjointClassesAxiom;
import org.semanticweb.owlapi.model.OWLDisjointUnionAxiom;
import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
import org.semanticweb.owlapi.model.OWLObjectAllValuesFrom;
import org.semanticweb.owlapi.model.OWLObjectComplementOf;
import org.semanticweb.owlapi.model.OWLObjectExactCardinality;
import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
import org.semanticweb.owlapi.model.OWLObjectMaxCardinality;
import org.semanticweb.owlapi.model.OWLObjectMinCardinality;
import org.semanticweb.owlapi.model.OWLObjectOneOf;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom;
import org.semanticweb.owlapi.model.OWLObjectUnionOf;
import org.semanticweb.owlapi.model.OWLObjectVisitor;
import org.semanticweb.owlapi.model.OWLQuantifiedDataRestriction;
import org.semanticweb.owlapi.model.OWLQuantifiedObjectRestriction;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;

import owl2vowl.constants.PropertyAllSomeValue;
import owl2vowl.constants.VowlAttribute;
import owl2vowl.model.data.VowlData;
import owl2vowl.model.entities.nodes.classes.AbstractClass;
import owl2vowl.model.entities.properties.AbstractProperty;
import owl2vowl.model.entities.properties.DatatypeValueReference;
import owl2vowl.model.entities.properties.ObjectValueReference;
import owl2vowl.parser.owlapi.IndividualsVisitor;

public class OwlClassAxiomVisitor implements OWLObjectVisitor {
	private VowlData vowlData;
	private OWLClass owlClass;
	private Logger logger = LogManager.getLogger(OwlClassAxiomVisitor.class);

	public OwlClassAxiomVisitor(VowlData vowlData, OWLClass owlClass) {
		this.vowlData = vowlData;
		this.owlClass = owlClass;
	}

	public void doDefault(Object object) {
		this.logger.info("Unsupported axiom: " + object);
	}

	public void visit(OWLEquivalentClassesAxiom axiom) {
		if (axiom.getNamedClasses().size() != 1) {
			this.createEquivalentClass(axiom);
		} else {
			OWLClass referencedClass = (OWLClass) axiom.getNamedClasses().iterator().next();
			Set expressionsWithoutRefClass = axiom.getClassExpressionsMinus(new OWLClassExpression[]{referencedClass});
			Iterator arg3 = expressionsWithoutRefClass.iterator();

			while (arg3.hasNext()) {
				OWLClassExpression anonymExpressions = (OWLClassExpression) arg3.next();
				anonymExpressions.accept(new OwlClassAxiomVisitor(this.vowlData, referencedClass));
			}

		}
	}

	private void createEquivalentClass(OWLEquivalentClassesAxiom axiom) {
		AbstractClass topClass = this.vowlData.getClassForIri(this.owlClass.getIRI());
		Iterator arg2 = axiom.getClassExpressionsMinus(new OWLClassExpression[]{this.owlClass}).iterator();

		while (arg2.hasNext()) {
			OWLClassExpression owlClassExpression = (OWLClassExpression) arg2.next();
			topClass.addEquivalentElement(owlClassExpression.asOWLClass().getIRI());
		}

		topClass.addAttribute(VowlAttribute.EQUIVALENT);
	}

	public void visit(OWLSubClassOfAxiom axiom) {
		if (axiom.isGCI()) {
			this.logger.info("Anonym subclass: " + axiom);
		} else {
			OWLClass subClass = axiom.getSubClass().asOWLClass();
			AbstractClass vowlSubclass = this.vowlData.getClassForIri(subClass.getIRI());
			if (axiom.getSuperClass().isAnonymous()) {
				axiom.getSuperClass().accept(new OwlClassAxiomVisitor(this.vowlData, this.owlClass));
			} else {
				OWLClass superClass = axiom.getSuperClass().asOWLClass();
				AbstractClass vowlSuperClass = this.vowlData.getClassForIri(superClass.getIRI());
				vowlSubclass.addSuperEntity(vowlSuperClass.getIri());
				vowlSuperClass.addSubEntity(vowlSubclass.getIri());
			}

		}
	}

	public void visit(OWLDisjointClassesAxiom axiom) {
		Iterator arg1 = axiom.asPairwiseAxioms().iterator();

		while (arg1.hasNext()) {
			OWLDisjointClassesAxiom pairwiseAxiom = (OWLDisjointClassesAxiom) arg1.next();
			IRI[] domainRange = new IRI[2];
			int index = 0;

			OWLClass aClass;
			for (Iterator arg5 = pairwiseAxiom.getClassesInSignature().iterator(); arg5
					.hasNext(); domainRange[index++] = aClass.getIRI()) {
				aClass = (OWLClass) arg5.next();
			}

			if (!this.vowlData.getSearcher().containsDisjoint(new IRI[]{domainRange[0], domainRange[1]})) {
				this.vowlData.getGenerator().generateDisjointProperty(domainRange[0], domainRange[1]);
			}
		}

	}

	public void visit(OWLDisjointUnionAxiom axiom) {
		if (axiom.getOWLClass().isAnonymous()) {
			this.logger.info("Disjoint Union base is anonym.");
		} else {
			AbstractClass baseClass = this.vowlData.getClassForIri(axiom.getOWLClass().getIRI());
			baseClass.addAttribute(VowlAttribute.DISJOINTUNION);
			Iterator arg2 = axiom.getOWLDisjointClassesAxiom().getClassesInSignature().iterator();

			while (arg2.hasNext()) {
				OWLClass disjointClass = (OWLClass) arg2.next();
				baseClass.addDisjointUnion(disjointClass.getIRI());
			}

		}
	}

	public void visit(OWLObjectMinCardinality ce) {
		if (!((OWLClassExpression) ce.getFiller()).isOWLThing()
				&& !((OWLClassExpression) ce.getFiller()).isOWLNothing()) {
			this.logger.info("Specification of cardinalities not supported yet: " + ce);
		} else {
			OWLObjectProperty property = ce.getProperty().getNamedProperty();
			AbstractProperty vowlProperty = this.vowlData.getPropertyForIri(property.getIRI());
			vowlProperty.setMinCardinality(Integer.valueOf(ce.getCardinality()));
		}
	}

	public void visit(OWLDataExactCardinality ce) {
		OWLDataPropertyExpression property = ce.getProperty();
		if (property.isAnonymous()) {
			this.logger.info("Anonymous dataproperty for exact cardinality.");
		} else {
			AbstractProperty vowlProperty = this.vowlData.getPropertyForIri(property.asOWLDataProperty().getIRI());
			vowlProperty.setExactCardinality(Integer.valueOf(ce.getCardinality()));
		}
	}

	public void visit(OWLDataMaxCardinality ce) {
		OWLDataPropertyExpression property = ce.getProperty();
		if (property.isAnonymous()) {
			this.logger.info("Anonymous dataproperty for max cardinality.");
		} else {
			AbstractProperty vowlProperty = this.vowlData.getPropertyForIri(property.asOWLDataProperty().getIRI());
			vowlProperty.setMaxCardinality(Integer.valueOf(ce.getCardinality()));
		}
	}

	public void visit(OWLDataMinCardinality ce) {
		OWLDataPropertyExpression property = ce.getProperty();
		if (property.isAnonymous()) {
			this.logger.info("Anonymous dataproperty for min cardinality.");
		} else {
			AbstractProperty vowlProperty = this.vowlData.getPropertyForIri(property.asOWLDataProperty().getIRI());
			vowlProperty.setMinCardinality(Integer.valueOf(ce.getCardinality()));
		}
	}

	public void visit(OWLDataAllValuesFrom ce) {
		this.processDataValueRestriction(ce, PropertyAllSomeValue.ALL);
	}

	public void visit(OWLDataSomeValuesFrom ce) {
		this.processDataValueRestriction(ce, PropertyAllSomeValue.SOME);
	}

	private void processDataValueRestriction(OWLQuantifiedDataRestriction ce, PropertyAllSomeValue value) {
		if (!((OWLDataRange) ce.getFiller()).isOWLDatatype()) {
			this.logger.info("DataValue range is not a datatype: " + ce);
		} else {
			OWLDatatype range = ((OWLDataRange) ce.getFiller()).asOWLDatatype();
			OWLDataProperty restrictedProperty = ce.getProperty().asOWLDataProperty();
			DatatypeValueReference valueReference = this.vowlData.getGenerator()
					.generateDatatypeValueReference(restrictedProperty.getIRI(), value);
			valueReference.addRange(this.vowlData.getGenerator().generateDatatypeReference(range.getIRI()).getIri());
			valueReference.addDomain(this.owlClass.getIRI());
		}
	}

	public void visit(OWLDataHasValue ce) {
		this.logger.info(ce + " not supported yet.");
	}

	public void visit(OWLObjectAllValuesFrom ce) {
		this.processObjectValueRestriction(ce, PropertyAllSomeValue.ALL);
	}

	public void visit(OWLObjectSomeValuesFrom ce) {
		this.processObjectValueRestriction(ce, PropertyAllSomeValue.SOME);
	}

	private void processObjectValueRestriction(OWLQuantifiedObjectRestriction ce, PropertyAllSomeValue value) {
		if (((OWLClassExpression) ce.getFiller()).isAnonymous()) {
			this.logger.info("ObjectAllValuesFrom range class is anonymous: " + ce);
		} else {
			OWLClass rangeClass = ((OWLClassExpression) ce.getFiller()).asOWLClass();
			OWLObjectProperty restrictedProperty = ce.getProperty().getNamedProperty();
			ObjectValueReference objectValueReference = this.vowlData.getGenerator()
					.generateObjectValueReference(restrictedProperty.getIRI(), value);
			objectValueReference.addRange(rangeClass.getIRI());
			objectValueReference.addDomain(this.owlClass.getIRI());
		}
	}

	public void visit(OWLObjectMaxCardinality ce) {
		if (!((OWLClassExpression) ce.getFiller()).isOWLThing()
				&& !((OWLClassExpression) ce.getFiller()).isOWLNothing()) {
			this.logger.info("Specification of cardinalities not supported yet: " + ce);
		} else {
			OWLObjectProperty property = ce.getProperty().getNamedProperty();
			AbstractProperty vowlProperty = this.vowlData.getPropertyForIri(property.getIRI());
			vowlProperty.setMaxCardinality(Integer.valueOf(ce.getCardinality()));
		}
	}

	public void visit(OWLObjectExactCardinality ce) {
		if (!((OWLClassExpression) ce.getFiller()).isOWLThing()
				&& !((OWLClassExpression) ce.getFiller()).isOWLNothing()) {
			this.logger.info("Specification of cardinalities not supported yet: " + ce);
		} else {
			OWLObjectProperty property = ce.getProperty().getNamedProperty();
			AbstractProperty vowlProperty = this.vowlData.getPropertyForIri(property.getIRI());
			vowlProperty.setExactCardinality(Integer.valueOf(ce.getCardinality()));
		}
	}

	public void visit(OWLObjectUnionOf ce) {
		Set operands = ce.getOperands();
		AbstractClass node = this.vowlData.getClassForIri(this.owlClass.getIRI());
		Iterator arg3 = operands.iterator();

		while (arg3.hasNext()) {
			OWLClassExpression operand = (OWLClassExpression) arg3.next();
			if (!operand.isAnonymous()) {
				node.addElementToUnion(operand.asOWLClass().getIRI());
				node.addAttribute(VowlAttribute.UNION);
			} else {
				this.logger.info("Anonymous exists in unions.");
			}
		}

	}

	public void visit(OWLObjectComplementOf ce) {
		if (ce.getOperand().isAnonymous()) {
			this.logger.info("Anonymous operand in object complement of.");
		} else {
			IRI baseClassIri = ce.getOperand().asOWLClass().getIRI();
			IRI complementIri = this.owlClass.getIRI();
			this.vowlData.getClassForIri(complementIri).addComplement(baseClassIri);
			this.vowlData.getClassForIri(complementIri).addAttribute(VowlAttribute.COMPLEMENT);
		}
	}

	public void visit(OWLObjectIntersectionOf ce) {
		Set operands = ce.getOperands();
		AbstractClass node = this.vowlData.getClassForIri(this.owlClass.getIRI());
		Iterator arg3 = operands.iterator();

		while (arg3.hasNext()) {
			OWLClassExpression operand = (OWLClassExpression) arg3.next();
			if (!operand.isAnonymous()) {
				node.addElementToIntersection(operand.asOWLClass().getIRI());
				node.addAttribute(VowlAttribute.INTERSECTION);
			} else {
				this.logger.info("Anonymous exists in intersections.");
			}
		}

	}

	public void visit(OWLObjectOneOf ce) {
		ce.getIndividuals().forEach((owlIndividual) -> {
			owlIndividual.accept(
					new IndividualsVisitor(this.vowlData, owlIndividual, this.owlClass, this.vowlData.getOwlManager()));
		});
	}
}